# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import datetime
import logging
import sys
import tabulate
from concurrent.futures import ProcessPoolExecutor

from coin.base.param_util import to_list

from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct

from coin.strategy.mm.dumper_base import run_from_archive
from coin.strategy.mm.passive_strategy_sim.base import (SimExecutor, SimStrategy)
from coin.exchange.base.tick import BitfinexBTCUSDTickPrice


def launch(start_time, hours, param_group, postfix=None):
  # basis_ma_window, edge, lot_size_0, lot_size_1, reserve
  product_1 = BitfinexProduct.FromStr('BTC-USDT')
  fee_1 = 0. / 10000.
  product_2 = BitmexFuturesProduct.FromStr('BTC-USD.PERPETUAL')
  fee_2 = -2.5 / 10000.

  postfix = postfix or ''
  if postfix and not postfix.endswith('_'):
    postfix = postfix + '_'

  sims = []
  for param in to_list(param_group):
    executor = SimExecutor()
    executor.set_exchange(1,
                          'bitfinex',
                          product_1,
                          fee_1,
                          execution_delay_sec=0.7,
                          post_only=True,
                          trade_qty_func=lambda trade: trade.qty)
    executor.set_exchange(2,
                          'bitmex',
                          product_2,
                          fee_2,
                          execution_delay_sec=1.0,
                          post_only=True,
                          trade_qty_func=lambda trade: trade.qty / trade.price)
    strategy = SimStrategy(executor,
                           basis_ma_window=param['basis_ma_window'] * 60 * (10**9),
                           reserve=param['reserve'],
                           imbalance_allowance=param['reserve'] * param['imbalance_allowance'],
                           postfix='%s%s_%dH' % (postfix, start_time.strftime('%Y%m%d'), hours),
                           start_ts=start_time + datetime.timedelta(minutes=45))

    strategy.set_exchange(  # Bitfinex
        1,
        edge=param['edge_1'] / 10000.,
        lot_size=param['lot_size_1'],
        tick=BitfinexBTCUSDTickPrice(),
        book_askt_func=lambda book: book.get_notional_asks_by_amt(param['true_book_1'])[1][-1][0],
        book_bidt_func=lambda book: book.get_notional_bids_by_amt(param['true_book_1'])[1][-1][0])
    strategy.set_exchange(  # BitMEX
        2,
        edge=param['edge_2'] / 10000.,
        lot_size=param['lot_size_2'],
        tick=0.5,
        book_askt_func=lambda book: book.get_notional_asks_by_qty(param['true_book_2'])[1][-1][0],
        book_bidt_func=lambda book: book.get_notional_bids_by_qty(param['true_book_2'])[1][-1][0])
    strategy.init_dump(dump_dir='dump_fff')
    sims.append((executor, strategy))

  def on_book_reset(book_builder_name, book_builder):
    for executor, _ in sims:
      executor.on_book_reset(book_builder_name, book_builder)

  run_from_archive([product_1, product_2],
                   on_book_reset,
                   start_time,
                   start_time + datetime.timedelta(hours=hours),
                   machine=['feed-3.eu-west-1', 'feed-6.eu-west-1'],
                   root_dir='/home/ec2-user/data/flow')

  results = []
  for executor, strategy in sims:
    res = executor.get_summary()
    res['param'] = strategy.get_filename()
    results.append(res)
  return results


def main(argv):
  num_param_per_group = 2
  param_group_list = []

  # Make param_group_list
  param_group = []
  scale_factor = 5
  # TODO(inkyu): Change me to USD unit
  for month, date in [(3, 7), (3, 8), (3, 9), (3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15),
                      (3, 16), (3, 17), (3, 18), (3, 19)]:
    start_time = datetime.datetime(2018, month, date, 0, 0, 0)
    lot_size_1 = 0.1 * scale_factor
    lot_size_2 = 0.1 * scale_factor
    imbalance_allowance = 0.1
    edge_diff = 0
    reserve_usd = 2. * scale_factor

    for ma_window in [30]:
      for edge in [20]:
        param = {
            'basis_ma_window': ma_window,
            'edge_1': edge + edge_diff,
            'edge_2': edge - edge_diff,
            'lot_size_1': lot_size_1,
            'lot_size_2': lot_size_2,
            'reserve': reserve_usd,
            'imbalance_allowance': imbalance_allowance,
        }
        if ((len(param_group) == num_param_per_group
             or (param_group and param_group[-1][0] != start_time))):
          param_group_list.append(param_group)
          param_group = []
        param_group.append((start_time, param))

  if param_group:
    param_group_list.append(param_group)

  # Run
  executor = ProcessPoolExecutor(max_workers=12)
  result_futs = []
  for time_param_pair_list in param_group_list:
    start_time = time_param_pair_list[0][0]
    param_group = [param for _, param in time_param_pair_list]
    hours = 24
    f = executor.submit(launch, start_time, hours, param_group)
    result_futs.append(f)

  headers = ['param_str', 'num_trade_1', 'qty_sum_1', 'num_trade_2', 'qty_sum_2', 'pnl']
  table = []
  for f in result_futs:
    result_list = f.result()
    for result in result_list:
      param_str = result['param']
      num_trade_1 = result['num_trade_1']
      qty_sum_1 = result['qty_sum_1']
      num_trade_2 = result['num_trade_2']
      qty_sum_2 = result['qty_sum_2']
      pnl = result['pnl']
      table.append((param_str, num_trade_1, qty_sum_1, num_trade_2, qty_sum_2, pnl))
  table = sorted(table)

  print('\n\n\n')
  print(tabulate.tabulate(table, headers=headers))

  print('\n\n\n')
  print(','.join(headers))
  for t in table:
    print('"%s",%d,%.4f,%d,%.4f,%.2f' % t)


if __name__ == '__main__':
  logging.basicConfig(level='DEBUG', format='%(levelname)8s %(asctime)s %(name)s] %(message)s')
  try:
    sys.exit(main(sys.argv))
  except KeyboardInterrupt:
    sys.exit(1)
